# -*- coding: utf-8 -*-

from decimal import Decimal
from functools import partial

from common.utils import math_helper
from common.lottery.cyclical.sd_11x5.model.order import BET_TYPE

RATE_CONFIG = {  # base on UNIT==2
    BET_TYPE.TWO_ANY: 5.500,
    BET_TYPE.THREE_ANY: 16.500,
    BET_TYPE.FOUR_ANY: 66.000,
    BET_TYPE.FIVE_ANY: 462.000,
    BET_TYPE.SIX_ANY: 77.000,
    BET_TYPE.SEVEN_ANY: 22.000,
    BET_TYPE.EIGHT_ANY: 8.250,
    BET_TYPE.ONE_FRONT: 11.000,
    BET_TYPE.TWO_FRONT_DIRECT: 110.000,
    BET_TYPE.TWO_FRONT_GROUP: 55.000,
    BET_TYPE.THREE_FRONT_DIRECT: 990.000,
    BET_TYPE.THREE_FRONT_GROUP: 165.000,
    BET_TYPE.ONE_ANY: '2.72',
}

UNIT = 2  # 彩票单价


def valid_number(bet_type, number):
    """由于存在两位数，约定同位之间使用逗号隔开，不用位之间使用'|'隔开，数字不需要前导0
    """
    positions = number.split('|')
    nums = []
    for position in positions:
        ds = position.split(',')
        for d in ds:
            assert 1 <= int(d) <= 11, 'number range error, should be 1~11'
        nums.append(ds)

    if BET_TYPE.TWO_ANY <= bet_type <= BET_TYPE.EIGHT_ANY:
        # 最少bet_type + 1个，最多11个
        assert len(positions) == 1, 'no `|` excepted'
        assert bet_type + 1 <= len(nums[0]) <= 11, 'number count error'
        assert len(set(nums[0])) == len(nums[0]), 'number can not include dup'
    elif bet_type == BET_TYPE.ONE_ANY:
        assert len(positions) == 1, 'no `|` excepted'
        assert 1 <= len(nums[0]) <= 11, 'number count error'
    elif bet_type == BET_TYPE.ONE_FRONT:
        # 1 ~ 11个
        assert len(positions) == 1, 'one front number format error'
        assert 1 <= len(nums[0]) <= 11, 'one front number invalid'
    elif bet_type == BET_TYPE.TWO_FRONT_DIRECT:
        assert len(positions) == 2, 'two front direct number format error'
        for num_list in nums:
            assert 1 <= len(num_list) <= 11, 'two front direct number invalid'
    elif bet_type == BET_TYPE.TWO_FRONT_GROUP:
        assert len(positions) == 1, 'two front group number format error'
        assert 2 <= len(nums[0]) <= 11, 'two front group number format error'
        assert len(set(nums[0])) == len(nums[0]), 'number can not include dup'
    elif bet_type == BET_TYPE.THREE_FRONT_DIRECT:
        assert len(positions) == 3, 'three front direct number format error'
        for num_list in nums:
            assert 1 <= len(num_list) <= 11, 'three front direct number error'
    elif bet_type == BET_TYPE.THREE_FRONT_GROUP:
        assert len(positions) == 1, 'three front group number format error'
        assert 3 <= len(nums[0]) <= 11, 'three front group number format error'
        assert len(set(nums[0])) == len(nums[0]), 'number can not include dup'
    else:
        assert False, 'bet_type invalid'


def calc_total_price(bet_type, number, times=1, unit=UNIT):
    """根据不同的竞猜方式计算所需总价
    """
    positions = number.split('|')
    nums = []
    for portion in positions:
        nums.append(set(portion.split(',')))
    if BET_TYPE.TWO_ANY <= bet_type <= BET_TYPE.EIGHT_ANY:
        # 任选N，则为c(n-1, N)个组合
        return math_helper.c(len(nums[0]), bet_type + 1) * times * unit
    elif bet_type == BET_TYPE.ONE_ANY:
        return len(nums[0]) * times * unit
    elif bet_type == BET_TYPE.ONE_FRONT:
        return len(nums[0]) * times * unit
    elif bet_type == BET_TYPE.TWO_FRONT_DIRECT:
        base = len(nums[0]) * len(nums[1]) - len(nums[0] & nums[1])
        return base * times * unit
    elif bet_type == BET_TYPE.TWO_FRONT_GROUP:
        # 任意2个的组合，c(n, 2)
        return math_helper.c(len(nums[0]), 2) * times * unit
    elif bet_type == BET_TYPE.THREE_FRONT_DIRECT:
        base = len(nums[0]) * len(nums[1]) * len(nums[2]) - len(
            nums[0] & nums[1]) * len(nums[2]) - len(
            nums[1] & nums[2]) * len(nums[0]) - len(
            nums[0] & nums[2]) * len(nums[1]) + 2 * len(
            nums[0] & nums[1] & nums[2])
        return base * times * unit
    elif bet_type == BET_TYPE.THREE_FRONT_GROUP:
        return math_helper.c(len(nums[0]), 3) * times * unit
    else:
        assert False, 'bet_type %s invalid' % bet_type


def generate_win_func(number):
    """根据开奖结果生成根据押注方式对应的中奖函数，避免重复计算
    """
    result = {}
    nums = number.split(',')

    def n_any(n, x):
        '''
        任选N玩法计算中奖金额
        :param n: 任选3时为3，任选4时为4...
        :param x: 用户押注的号码
        '''
        guesses = x.split(',')
        hits = len(set(guesses) & set(nums))
        if 5 > n > hits:
            return 0
        elif n >= 5:
            return math_helper.c(len(guesses) - 5 ,n - 5) if hits == 5 else 0
        return math_helper.c(hits, n)
    for n in xrange(BET_TYPE.TWO_ANY, BET_TYPE.EIGHT_ANY + 1):
        result[n] = partial(n_any, n + 1)

    def one_any(x):
        guesses = x.split(',')
        hits = len(set(guesses) & set(nums))
        return hits
    result[BET_TYPE.ONE_ANY] = one_any

    def one_front(x):
        return 1 if nums[0] in x.split(',') else 0
    result[BET_TYPE.ONE_FRONT] = one_front

    def n_front_direct(n, x):
        targets = nums[:n]
        if len(set(targets)) < n:
            return 0
        guesses = x.split('|')
        all_in = True
        for i, target in enumerate(targets):
            if target not in guesses[i].split(','):
                all_in = False
                break
        return 1 if all_in else 0

    result[BET_TYPE.TWO_FRONT_DIRECT] = partial(n_front_direct, 2)
    result[BET_TYPE.THREE_FRONT_DIRECT] = partial(n_front_direct, 3)

    def n_front_group(n, x):
        targets = nums[:n]
        if len(set(targets)) != n:
            return 0
        guesses = x.split(',')
        all_in = True
        for target in targets:
            if target not in guesses:
                all_in = False
                break
        return 1 if all_in else 0

    result[BET_TYPE.TWO_FRONT_GROUP] = partial(n_front_group, 2)
    result[BET_TYPE.THREE_FRONT_GROUP] = partial(n_front_group, 3)

    return result


def calc_win_price(funcs, bet_number, bet_type, times=1, unit=UNIT):
    """根据开奖结果和押注方式/倍数，计算中奖金额
    """
    func = funcs.get(bet_type)
    base = func(bet_number) * Decimal(RATE_CONFIG.get(bet_type))

    return Decimal(base) * Decimal(times) * Decimal(unit) / Decimal(UNIT)
